package com.zxj.job.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.zxj.common.core.contains.CacheConstants;
import com.zxj.common.core.contains.Constants;
import com.zxj.common.redis.service.RedisService;
import com.zxj.job.domain.exam.Exam;
import com.zxj.job.domain.message.entity.Message;
import com.zxj.job.domain.message.entity.MessageText;
import com.zxj.job.domain.message.vo.MessageTextVO;
import com.zxj.job.domain.user.UserExam;
import com.zxj.job.domain.user.UserScore;
import com.zxj.job.mapper.exam.ExamMapper;
import com.zxj.job.mapper.user.UserExamMapper;
import com.zxj.job.mapper.user.UserSubmitMapper;
import com.zxj.job.service.message.IMessageService;
import com.zxj.job.service.message.IMessageTextService;
import com.zxj.job.service.user.IUserExamService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
@RequiredArgsConstructor
public class ExamXxlJob {

    private final ExamMapper examMapper;

    private final RedisService redisService;

    private final UserSubmitMapper userSubmitMapper;

    private final UserExamMapper userExamMapper;

    private final IMessageTextService messageTextService;

    private final IMessageService messageService;

    private final IUserExamService userExamService;

    /**
     * 定时任务：整理竞赛列表
     */
    @XxlJob("examListOrganizeHandler")
    public void examListOrganizeHandler() {
        //  统计哪些竞赛应该存入未完赛的列表中  哪些竞赛应该存入历史竞赛列表中   统计出来了之后，再存入对应的缓存中
        log.info("*** 定时任务：整理竞赛列表 ***");
        List<Exam> unFinishList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .gt(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(unFinishList, CacheConstants.EXAM_UNFINISHED_LIST);

        List<Exam> historyList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .le(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));

        refreshCache(historyList, CacheConstants.EXAM_HISTORY_LIST);
    }


    /**
     * 定时任务：竞赛结束后, 统计排名情况并发送消息
     */
    @XxlJob("examResultHandler")
    public void examResultHandler() {
        log.info("*** 定时任务：竞赛结束后, 统计排名情况并发送消息 ***, 当前时间：{}", LocalDateTime.now());
        LocalDateTime now = LocalDateTime.now(); // 当前时间
        LocalDateTime minusDateTime = now.minusDays(1); // 当前时间减去一天
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle) // 查询竞赛ID和标题
                .eq(Exam::getStatus, Constants.TRUE) // 竞赛状态为正常
                .ge(Exam::getEndTime, minusDateTime) // 竞赛结束时间大于等于当前时间减去一天
                .le(Exam::getEndTime, now)); // 竞赛结束时间小于等于当前时间
        if (CollectionUtil.isEmpty(examList)) { // 前一天没有竞赛结束
            return;
        }
        // 获取竞赛ID集合
        Set<Long> examIdSet = examList.stream().map(Exam::getExamId).collect(Collectors.toSet());
        // 查询用户分数列表, 获得每个竞赛的用户分数列表, SQL语句如下:
        // select exam_id, user_id,sum(score) as score from tb_user_submit
        // where exam_id in (examIdSet) group by exam_id, user_id order by score desc
        List<UserScore> userScoreList = userSubmitMapper.selectUserScoreList(examIdSet);
        // 将用户分数列表按照竞赛ID分组, 得出每个竞赛用户的排名情况
        Map<Long, List<UserScore>> userScoreMap = userScoreList.stream()
                .collect(Collectors.groupingBy(UserScore::getExamId));
        // 创建消息
        createMessage(examList, userScoreMap);
    }

    /**
     * 创建消息
     * @param examList 竞赛列表
     * @param userScoreMap 竞赛ID: 用户分数列表
     */
    private void createMessage(List<Exam> examList, Map<Long, List<UserScore>> userScoreMap) {
        List<MessageText> messageTextList = new ArrayList<>(); // 消息文本列表
        List<Message> messageList = new ArrayList<>(); // 消息列表
        for (Exam exam : examList) { // 针对每个竞赛进行排名统计
            Long examId = exam.getExamId(); // 竞赛ID
            List<UserScore> userScoreList = userScoreMap.get(examId); // 获取该竞赛的用户分数列表
            if (CollectionUtil.isEmpty(userScoreList)) {
                // 该竞赛没有用户参与, 跳过
                continue;
            }
            int totalUser = userScoreList.size(); // 参与该竞赛的总人数
            int examRank = 1; // 竞赛排名
            for (UserScore userScore : userScoreList) { // 遍历用户分数列表
                String msgTitle = exam.getTitle() + "——排名情况";
                String msgContent = "您所参与的竞赛：" + exam.getTitle()
                        + "，本次参与竞赛一共" + totalUser + "人， 您排名第" + examRank + "名！";
                userScore.setExamRank(examRank); // 设置用户排名
                // 创建消息文本
                MessageText messageText = new MessageText();
                messageText.setMessageTitle(msgTitle); // 设置消息标题
                messageText.setMessageContent(msgContent); // 设置消息内容
                messageText.setCreateBy(Constants.SYSTEM_USER_ID); // 设置创建者 - 系统用户
                messageTextList.add(messageText);  // 添加到消息文本列表
                // 创建消息
                Message message = new Message();
                message.setSendId(Constants.SYSTEM_USER_ID); // 设置发送者 - 系统用户
                message.setCreateBy(Constants.SYSTEM_USER_ID);  // 设置创建者 - 系统用户
                message.setUpdateBy(Constants.SYSTEM_USER_ID);  // 设置更新者 - 系统用户
                message.setRecId(userScore.getUserId()); // 设置接收者 - 用户ID
                messageList.add(message); // 添加到消息列表
                examRank++;
            }
            // 更新该竞赛的用户分数和排名
            //userExamMapper.batchUpdateUserScoreAndRank(userScoreList); 有bug
            userScoreList.forEach(userExamMapper::updateUserScoreAndRank);


            // 维护竞赛排名列表缓存, key: e:r:l:${examId}, value: [userScore1, userScore2, ...]
            redisService.rightPushAll(getExamRankListKey(examId), userScoreList);
        }
        // 批量保存消息文本, 返回保存后的消息文本ID
        messageTextService.saveBatch(messageTextList);

        // 批量保存消息
        Map<String, MessageTextVO> messageTextVOMap = new HashMap<>(); // 这个容器用来后续保存到Redis中的
        for (int i = 0; i < messageTextList.size(); i++) {
            // 创建消息文本VO
            MessageText messageText = messageTextList.get(i);
            MessageTextVO messageTextVO = BeanUtil.copyProperties(messageText, MessageTextVO.class);
            String msgDetailKey = getMsgDetailKey(messageText.getTextId());
            messageTextVOMap.put(msgDetailKey, messageTextVO);

            // 保存消息列表对应的消息文本ID
            Message message = messageList.get(i);
            message.setTextId(messageText.getTextId());
        }
        // 批量保存消息
        messageService.saveBatch(messageList);
        // 批量保存消息文本到Redis中
        redisService.multiSet(messageTextVOMap);

        // 保存用户消息列表到Redis中
        Map<Long, List<Message>> userMsgMap = messageList.stream().collect(Collectors.groupingBy(Message::getRecId));
        for (Map.Entry<Long, List<Message>> entry : userMsgMap.entrySet()) {
            Long recId = entry.getKey();
            String userMsgListKey = getUserMsgListKey(recId);
            List<Long> userMsgTextIdList = entry.getValue().stream().map(Message::getTextId).toList();
            // key: u:m:l:${userId}, value: [textId1, textId2, ...]
            redisService.rightPushAll(userMsgListKey, userMsgTextIdList);
        }
    }


    /**
     * 刷新缓存
     * @param examList 竞赛列表
     * @param examListKey 缓存key
     */
    public void refreshCache(List<Exam> examList, String examListKey) {
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        Map<String, Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()), exam);
            examIdList.add(exam.getExamId());
        }
        redisService.multiSet(examMap);  //刷新详情缓存
        redisService.deleteObject(examListKey);
        redisService.rightPushAll(examListKey, examIdList);      //刷新列表缓存
    }

    /**
     * 获取详情缓存key
     * @param examId 竞赛ID
     * @return 缓存key
     */
    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    /**
     * 获取用户消息列表缓存key
     * @param userId 用户ID
     * @return 缓存key u:m:l:${userId}
     */
    private String getUserMsgListKey(Long userId) {
        return CacheConstants.USER_MESSAGE_LIST + userId;
    }

    /**
     * 获取消息详情缓存key
     * @param textId 消息ID
     * @return 缓存key m:d:${textId}
     */
    private String getMsgDetailKey(Long textId) {
        return CacheConstants.MESSAGE_DETAIL + textId;
    }

    /**
     * 获取竞赛排名列表缓存key
     * @param examId 竞赛ID
     * @return 缓存key e:r:l:${examId}
     */
    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST + examId;
    }

}
